package org.third.spring.schedule;

import java.io.IOException;
import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Transient;
import javax.validation.constraints.Size;

import org.hibernate.annotations.Type;
import org.hibernate.validator.constraints.NotEmpty;
import org.opensaml.saml2.metadata.Organization;
import org.third.common.utils.JsonUtils;
import org.third.common.utils.SecurityUtils;
import org.third.common.utils.StringUtils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * Metadata class to hold key-value pairs of Organization Metadata where the
 * keys and values
 */
public class Metadata extends PersistedObject implements Comparable, Serializable {

	/** The JSON root name */
	public static final String RootName = "metadata";
	/** Database table name */
	public static final String TableName = "METADATA";
	/** Metadata Key Attribute name */
	public static final String KeyAttributeName = "key";
	/** Metadata Key Column name */
	public static final String KEY_COLUMN_NAME = "METADATA_KEY";
	/** Metadata Value Attribute name */
	public static final String ValueAttributeName = "value";
	/** Metadata Value Column name */
	public static final String VALUE_COLUMN_NAME = "VALUE";
	/** Metadata Public Flag Attribute name */
	public static final String PublicAttributeName = "public";
	/** Metadata Public Flag Column name */
	public static final String PUBLIC_COLUMN_NAME = "IS_PUBLIC";
	/**
	 * The flag indicates if the group information within the issued HPSSO token.
	 */
	public static final String ADD_GROUPS_INTO_HPSSO_COOKIE = "addGroupsIntoHpssoCookie";
	/**
	 * The flag indicates if the permission information within the issued HPSSO
	 * token.
	 */
	public static final String ADD_PERMISSIONS_INTO_HPSSO_COOKIE = "addPermissionsIntoHpssoCookie";
	/** The flag indicates if the role information within the issued HPSSO token. */
	public static final String ADD_ROLES_INTO_HPSSO_COOKIE = "addRolesIntoHpssoCookie";
	/** The flag indicates if the signup button should be displayed or not. */
	public static final String ENABLE_DB_USER_SIGNUP = "enableDbUserSignup";
	/**
	 * The default group for an organization. Database users in an organization are
	 * automatically associated with the default group for the organization to get
	 * default roles and permissions.
	 */
	public static final String DEFAULT_SIGNUP_DB_USER_GROUP = "defaultSignupDbUserGroup";
	/**
	 * The flag indicates if the Legacy LWSSO cookie will be accepted for
	 * organization when HPSSO receive the legacy lwsso cookie. if it is empty or
	 * false, this orgazation will not accept the single-sign-on from legacy lwsso
	 * app. if it is true, IdM will accept the legacy lwsso cookie and recreate the
	 * idm lwsso cookie.
	 */
	public static final String ACCPET_LEGACY_LWSSO_COOKIE = "acceptLegacyLwssoCookie";

	/**
	 * Compares this object with the specified object for order. Returns a negative
	 * integer, zero, or a positive integer as this object is less than, equal to,
	 * or greater than the specified object.
	 *
	 * @param o
	 *            the object to be compared.
	 * @return a negative integer, zero, or a positive integer as this object is
	 *         less than, equal to, or greater than the specified object.
	 *
	 * @throws NullPointerException
	 *             if the specified object is null
	 * @throws ClassCastException
	 *             if the specified object's type prevents it from being compared to
	 *             this object.
	 */
	@Override
	public int compareTo(Object o) {
		if (this == o)
			return 0;
		if (null == o)
			return 1;

		Metadata that = (Metadata) o;

		if (this.getKey() == null && that.getKey() != null)
			return -1;
		if (this.getKey() != null && that.getKey() == null)
			return 1;
		if (this.getKey() != null) {
			int keyCompare = this.getKey().compareTo(that.getKey());
			if (keyCompare != 0)
				return keyCompare;
		}

		if (this.getValue() == null && that.getValue() != null)
			return -1;
		if (this.getValue() != null && that.getValue() == null)
			return 1;
		if (this.getValue() != null) {
			int valueCompare = this.getValue().compareTo(that.getValue());
			if (valueCompare != 0)
				return valueCompare;
		}

		if (this.isPublic() && !that.isPublic())
			return -1;
		if (!this.isPublic() && that.isPublic())
			return 1;

		return 0;
	}

	/**
	 * Determines whether this Metadata Object is considered equal to the given
	 * Metadata Object
	 *
	 * @param obj
	 *            another Metadata Object
	 *
	 * @return {@code true} if the Metadata Objects are considered equal;
	 *         {@code false} otherwise
	 */
	@SuppressWarnings("all")
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || getClass() != obj.getClass())
			return false;

		Metadata that = (Metadata) obj;

		return this.compareTo(that) == 0 ? true : false;
	}

	/**
	 * Computes a hash code for this Organization
	 *
	 * @return the hash code
	 */
	@Override
	public int hashCode() {
		return 31 * (this.key != null ? this.key.hashCode() : 0) + (this.value != null ? this.value.hashCode() : 0);
	}

	/**
	 * Default constructor for Metadata
	 */
	public Metadata() {
		super();
		markPublic();
	}

	/**
	 * Constructor for Metadata with {@link java.lang.String} key specified only.
	 *
	 * @param key
	 *            {@link java.lang.String} to use as the metadata key
	 */
	public Metadata(String key) {
		super();
		setKey(key);
		setValue("");
		markPublic();
	}

	/**
	 * Constructor for Metadata with key and value specified.
	 *
	 * @param key
	 *            {@link java.lang.String} to use as the metadata key
	 * @param value
	 *            {@link java.lang.String} to use as the metadata value
	 */
	public Metadata(String key, String value) {
		super();
		setKey(key);
		setValue(value == null ? null : value.trim());
		markPublic();
	}

	/**
	 * Constructor for Metadata with key, value, and public flag specified.
	 *
	 * @param key
	 *            {@link java.lang.String} to use as the metadata key
	 * @param value
	 *            {@link java.lang.String} to use as the metadata value
	 * @param publicFlag
	 *            {@code true} or {@code false}
	 */
	public Metadata(String key, String value, boolean publicFlag) {
		super();
		setKey(key);
		setValue(value == null ? null : value.trim());
		setPublic(publicFlag);
	}

	/**
	 * Update this Metadata object from a new Metadata object
	 *
	 * @param newMetadata
	 *            the new Metadata object
	 */
	public void update(Metadata newMetadata) {
		setKey(newMetadata.getKey());
		setValue(newMetadata.getValue() == null ? null : newMetadata.getValue().trim());
		setPublic(newMetadata.isPublic());
	}

	// TODO fix the json ignore for ID, might have to move ID back to this file for
	// idm(1.7)

	private String key;

	/**
	 * Get the Metadata key value.
	 *
	 * @return {@link java.lang.String} key
	 */
	@Id
	@JsonProperty("key")
	@Column(name = Metadata.KEY_COLUMN_NAME, length = 900)
	@Type(type = COLUMN_TYPE_NSTRING)
	@NotEmpty
	@Size(max = 900)
	public String getKey() {
		return this.key;
	}

	/**
	 * Set the Metadata key value.
	 *
	 * @param key
	 *            the {@link java.lang.String} to set as the key
	 */
	public void setKey(String key) {
		this.key = key;
	}

	/**
	 * Determine if the Metadata key is set.
	 *
	 * @return {@code true} if set, {@code false} otherwise
	 */
	public boolean hasKey() {
		return StringUtils.hasText(getKey());
	}

	private String value;

	/**
	 * Get the Metadata value String.
	 *
	 * @return {@link java.lang.String} value
	 */
	@JsonProperty("value")
	@Column(name = Metadata.VALUE_COLUMN_NAME, length = 4096)
	@Type(type = COLUMN_TYPE_NSTRING)
	@Size(max = 4096)
	public String getValue() {
		return this.value;
	}

	/**
	 * Set the Metadata value String.
	 *
	 * @param value
	 *            {@link java.lang.String} to use as the value
	 */
	public void setValue(String value) {
		this.value = value == null ? null : value.trim();
	}

	/**
	 * Determine if the Metadata value is set.
	 *
	 * @return {@code true} if set, {@code false} otherwise
	 */
	public boolean hasValue() {
		return StringUtils.hasText(getValue());
	}

	private Organization organization;

	private boolean publicFlag;

	/**
	 * Get the public flag setting
	 *
	 * @return {@code true} or {@code false}
	 */
	@JsonProperty("public")
	@Column(name = Metadata.PUBLIC_COLUMN_NAME)
	public boolean isPublic() {
		return this.publicFlag;
	}

	/**
	 * Set the public flag value
	 *
	 * @param publicFlag
	 *            {@code true} or {@code false}
	 */
	public void setPublic(boolean publicFlag) {
		this.publicFlag = publicFlag;
	}

	/**
	 * Set the public flag value to {@code true}
	 */
	public void markPublic() {
		setPublic(true);
	}

	/**
	 * Set the public flag value to {@code false}
	 */
	public void markPrivate() {
		setPublic(false);
	}

	/**
	 * Get the {@link com.hp.ccue.identity.domain.organization.Organization} that
	 * this metadata belongs to
	 *
	 * @return {@link com.hp.ccue.identity.domain.organization.Organization}
	 */
	@JsonIgnore
	public Organization getOrganization() {
		return this.organization;
	}

	/**
	 * Set the {@link com.hp.ccue.identity.domain.organization.Organization} that
	 * this metadata belongs to
	 *
	 * @param organization
	 *            {@link com.hp.ccue.identity.domain.organization.Organization} that
	 *            will have this metadata in its list of metadata
	 */
	public void setOrganization(Organization organization) {
		this.organization = organization;
	}

	/**
	 * Determines if this Metadata has an
	 * {@link com.hp.ccue.identity.domain.organization.Organization}
	 *
	 * @return {@code true} if the
	 *         {@link com.hp.ccue.identity.domain.organization.Organization} is set,
	 *         {@code false} otherwise
	 */
	public boolean hasOrganization() {
		return this.getOrganization() != null;
	}

	/**
	 * Return a {@link java.lang.String} representation of this Metadata object
	 *
	 * @return {@link java.lang.String} formatted as JSON
	 */
	@Override
	public String toString() {
		try {
			return this.toJson();
		} catch (IOException e) {
			return "metadata: " + this.getKey();
		}
	}

	/**
	 * Return a JSON representation of this Metadata object
	 *
	 * @return {@link String} formatted as JSON
	 *
	 * @throws IOException
	 */
	public String toJson() throws IOException {
		return JsonUtils.marshall(this);
	}

	/**
	 * Computes the entity tag for this Metadata object. Entity tags can be used for
	 * conditional GET and concurrency control.
	 *
	 * @return the entity tag for this Metadata object.
	 */
	@JsonIgnore
	@Transient
	@Override
	public String getEntityTag() {
		String message = StringUtils.join(" ", this.getKey(), this.getValue(), this.isPublic(), this.getOrganization());
		return SecurityUtils.hashAsSha1(message);
	}
}
